package bsk;

import java.io.*;
import java.math.BigInteger;
import java.net.Socket;
import java.util.Random;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.net.ssl.SSLServerSocket;
import javax.net.ssl.SSLServerSocketFactory;
import komunikaty.Card;
import komunikaty.Kandydaci;
import komunikaty.Upowazniony;
import komunikaty.Statistics;

public class Connect {

    private final static int SSL_ECHO_PORT = 443;
    //JKS keystore holding the server's private key  and its associated public key certificate.
    private String KS_PATH;// = "server.jks";
    private String KS_PASS;// = "123456";

    public Connect(String KS_path, String KS_pass) {
        this.KS_PATH = KS_path;
        this.KS_PASS = KS_pass;
    }

    public void run() throws Exception {
        System.setProperty("javax.net.ssl.keyStore", KS_PATH);
        System.setProperty("javax.net.ssl.keyStorePassword", KS_PASS);

        SSLServerSocketFactory factory = (SSLServerSocketFactory) SSLServerSocketFactory.getDefault();
        SSLServerSocket server = (SSLServerSocket) factory.createServerSocket(SSL_ECHO_PORT);

//        if (clientAuth) server.setNeedClientAuth(true);
        while (true) {
            Socket socket = server.accept();
            Thread t = new Thread(new ConnectionHandler(socket));
            t.start();
        }
    }
}

class ConnectionHandler implements Runnable {

    private Socket socket;
    private ObjectOutputStream out;
    private ObjectInputStream in;
    //poĹ‚aczenie (authorization = true), pobranie danych (doGlosowania = true), gĹ‚osowanie (glosowanie = true), publikacja (publikacja = true)
    public boolean[] CoRobic = new boolean[4];

    public ConnectionHandler(Socket socket) {
        this.socket = socket;
    }

    @Override
    public void run() {
        try {
            //dane poĹ‚Ä…czenia, certyfikaty, itd.
//            SSLSession session = ((SSLSocket) socket).getSession();
//            java.security.cert.Certificate[] cchain2 = session.getLocalCertificates();
//            for (int i = 0; i < cchain2.length; i++) {
//                System.out.println(((X509Certificate) cchain2[i]).getSubjectDN());
//            }
//            System.out.println("Peer host is " + session.getPeerHost());
//            System.out.println("Cipher is " + session.getCipherSuite());
//            System.out.println("Protocol is " + session.getProtocol());
//            System.out.println("ID is " + new BigInteger(session.getId()));
//            System.out.println("Session created in " + session.getCreationTime());
//            System.out.println("Session accessed in " + session.getLastAccessedTime());

//            loadKeyStore("server.jks", "123456");


            out = new ObjectOutputStream(socket.getOutputStream());
            out.flush();
            in = new ObjectInputStream(socket.getInputStream());
            sendMessage("Connection successful");
            Object message = null;


            try {
                message = in.readObject();
                CoRobic = (boolean[]) message;
                for (boolean b : CoRobic) {
                    System.out.println(b);
                }
                sendMessage("OK");


                if (CoRobic[0]) {

                    //tutaj jest Tworzenie certyfikatu

                    message = in.readObject();
                    System.out.println("<wysĹ‚aĹ‚ klient>" + ((Upowazniony) message).toString());
                    boolean T = weryfikacja_klienta(message);
                    if (T) {
                        sendMessage("Mozesz głosować!");
                        //pobiera dane do wygenerowania certyfikatu
                        message = in.readObject();
                        System.out.println((String) message);

                        sendMessage("certyfikat");

                    } else {
                        sendMessage("Nie mozesz głosować!");
                    }

                } else if (CoRobic[1]) {

                    //tutaj jest sprawdzanie klienta, certyfikat

                    message = in.readObject();
                    System.out.println((String) message);
                    //odczytuje i wysyĹ‚a listÄ™ kandydatĂłw
                    Kandydaci[] Kand = getKandydat("Kadydaci.dat");
                    sendMessage(Kand);
                } else if (CoRobic[2]) {

                    //tutaj jest sprawdzanie klienta, certyfikat




                    message = (String) in.readObject();
                    System.out.println("<wysĹ‚aĹ‚ klient>" + message);

                    //odczytuje i wysyĹ‚a listÄ™ kandydatĂłw
                    Kandydaci[] Kand = getKandydat("Kadydaci.dat");
                    sendMessage(Kand);

                    message = in.readObject();
                    System.out.println((String) message);

                    //pobieranie klucza
                    BigInteger[] klu = OdczytKluczy("Klucze.dat");
                    for (BigInteger bigInteger : klu) {
                        System.out.println(bigInteger);
                    }

                    BigInteger E = klu[0];
                    BigInteger D = klu[1];
                    BigInteger N = klu[2];
                    BigInteger E2 = klu[3];
                    BigInteger D2 = klu[4];
                    BigInteger N2 = klu[5];

//                    //generowanie kluczy i moduĹ‚Ăłw
//                    KeyPair keypair = SVoting.generateRSAKeyPair();
//                    BigInteger E = SVoting.getPublicExponent(keypair);
//                    BigInteger D = SVoting.getPrivateExponent(keypair);
//                    BigInteger N = SVoting.getModul(keypair);
//                    //DRUGIE KLUCZE
//                    KeyPair KP = SVoting.generateRSAKeyPair();
//                    BigInteger E2 = SVoting.getPublicExponent(KP);
//                    BigInteger D2 = SVoting.getPrivateExponent(KP);
//                    BigInteger N2 = SVoting.getModul(KP);

                    //wysyĹ‚anie kluczy i moduĹ‚Ăłw
                    BigInteger[] KLUCZE = {E, N, E2, N2};
//                    for (int i = 0; i < KLUCZE.length; i++) {
//                        System.out.println(KLUCZE[i]);
//                    }
                    sendMessage(KLUCZE);

                    //pobieranie kart zaciemnionych
                    message = in.readObject();
                    Card[] kar = (Card[]) message;
//                    for (int i = 0; i < kar.length; i++) {
//                        System.out.println(kar[i].toString());
//                    }

                    //losowanie i wysyĹ‚anie ktĂłrÄ… ma podpisac (odĹ›lepianie pozostaĹ‚ych)
                    Random rand = new Random();
                    int ii = rand.nextInt(kar.length);
                    sendMessage(ii);

                    //odpiera tablice r do odciemnienia kart
                    message = in.readObject();
                    BigInteger[] TajneR = (BigInteger[]) message;
                    //oĹ›lepia karty
                    for (int j = 0; j < TajneR.length; j++) {
                        if (j != ii) {
                            SVoting.odciemniajKarte(kar[j], TajneR[j], E, N);
                        }
                    }

                    //sprawdzanie czy sÄ… poprawne karty
                    for (int i = 0; i < kar.length; i++) {
                        System.out.println(kar[i].toString());
                    }

                    //serwer podpisuje "ostatniÄ…" karte i wysyĹ‚a do klienta
                    SVoting.podpiszZaciKarte(kar[ii], D, N);
                    sendMessage(kar[ii]);

                    //odczytuje zakodowany gĹ‚os
                    message = in.readObject();

                    Card.Vote[] glos = new Card.Vote[1];
                    glos[0] = (Card.Vote) message;
                    Dopisz("Gosowanie.dat", glos);

                    //zapisuje gĹ‚os w pliku

                    //wysyĹ‚a komunikat oddania gĹ‚osu
                    sendMessage("GĹ‚os pozytywnie oddany");
                    //serwer odkodowuje gĹ‚os
                    SVoting.odkodowanieGlosu(glos[0], E, N, D2, N2);
                    System.out.println(glos[0].candidate + " " + glos[0].serial);

                } else if (CoRobic[3]) {
                    message = in.readObject();
                    System.out.println((String) message);

                    //pobieranie klucza
                    BigInteger[] klu = OdczytKluczy("Klucze.dat");
//                    for (BigInteger bigInteger : klu) {
//                        System.out.println(bigInteger);
//                    }

                    //pobranie gĹ‚osĂłw
                    ObjectInputStream ois = new ObjectInputStream(new FileInputStream("Gosowanie.dat"));
                    Card.Vote[] objects = (Card.Vote[]) ois.readObject();
                    ois.close();

                    //serwer odkodowuje gĹ‚os
                    for (Card.Vote vote : objects) {
                        SVoting.odkodowanieGlosu(vote, klu[0], klu[2], klu[4], klu[5]);
                    }
                    for (Card.Vote vote : objects) {
                        System.out.println(vote.candidate + " " + vote.serial);
                    }


                    //liczba upowaznionych do glosowania
                    ObjectInputStream ooinn = null;
                    Upowazniony[] objectss = null;
//                    int ilu_glosowalo = 0;
                    try {
                        ooinn = new ObjectInputStream(new FileInputStream("glosujacy.dat"));
                        objectss = (Upowazniony[]) ooinn.readObject();
                    } catch (ClassNotFoundException ex) {
                    } catch (IOException ex) {
                    } finally {
                        try {
                            ooinn.close();
                        } catch (IOException ex) {
                        }
                    }
                    int LiczbaGlosujacych = objectss.length;


//                    //ilu glosowalo
//                    for (int i = 0; i < objectss.length; i++) {
//                        if(objectss[i].getGlosowal()){
//                            ilu_glosowalo++;
//                        }
////                        System.out.println(objectss[i].toString());
//                    }
                    Statistics stat = new Statistics(objects, LiczbaGlosujacych);

//                    int [] TabDana = new int[2];
//                    TabDana[0] = LiczbaGlosujacych;
//                    TabDana[1] = ilu_glosowalo;
                    //wysĹ‚anie wynikĂłw
                    sendMessage(stat);
                    message = in.readObject();
                    System.out.println((String)message);
                    Kandydaci[] kandd = getKandydat("Kadydaci.dat");
                    sendMessage(kandd);
                }

            } catch (Exception ex) {
                Logger.getLogger(ConnectionHandler.class.getName()).log(Level.SEVERE, null, ex);
            }


        } catch (IOException ex) {
        } finally {
            try {
                in.close();
                out.close();
                socket.close();
            } catch (IOException ex) {
            }
        }
    }

    private synchronized void sendMessage(Object obj) {
        try {
            out.writeObject(obj);
            out.flush();
            //System.out.println("<wysĹ‚aĹ‚ server>   " + obj.toString());
        } catch (IOException ex) {
            System.out.println("bĹ‚Ä…d wysyĹ‚ania!!!");
            ex.printStackTrace();
        }
    }

    //weryfikacja klienta, sprawdza czy juĹĽ gĹ‚osowaĹ‚ jeĹ›li nie to zapisuje ĹĽe juĹĽ glosowaĹ‚
    private synchronized boolean weryfikacja_klienta(Object obj_klient) {
        ObjectInputStream oin = null;
        Upowazniony[] objects = null;
        try {
            oin = new ObjectInputStream(new FileInputStream("glosujacy.dat"));
            objects = (Upowazniony[]) oin.readObject();
        } catch (ClassNotFoundException ex) {
        } catch (IOException ex) {
        } finally {
            try {
                oin.close();
            } catch (IOException ex) {
            }
        }
        /////////////////////////////////////////////////////////////////////
        //System.out.println("rozmiar = "+ objects.length);                //
        //for (int i = 0; i < objects.length; i++) {                       //
        //    System.out.println((i+1)+".\t"+objects[i].toString());       //
        //}                                                                //
        ///////////////////////////////////////////////////////////////////
        boolean moze = false;
        for (int i = 0; i < objects.length; i++) {
            if (((Upowazniony) obj_klient).getPesel().equals(objects[i].getPesel()) && !objects[i].getGlosowal()) {
                objects[i].setGlosowal(true);
                moze = true;
                break;
            }
        }
        if (moze) {
            ObjectOutputStream oos = null;
            try {
                oos = new ObjectOutputStream(new FileOutputStream("glosujacy.dat"));
                oos.writeObject(objects);
                oos.flush();
            } catch (IOException ex) {
            } finally {
                try {
                    oos.close();
                } catch (IOException ex) {
                }
            }
        }
        return moze;
    }

    //pobieranie kandydatĂłw z pliku
    private synchronized Kandydaci[] getKandydat(String file) {
        ObjectInputStream ois = null;
        Kandydaci[] objects = null;
        try {
            ois = new ObjectInputStream(new FileInputStream(file));
            objects = (Kandydaci[]) ois.readObject();
        } catch (ClassNotFoundException ex) {
            Logger.getLogger(ConnectionHandler.class.getName()).log(Level.SEVERE, null, ex);
        } catch (IOException ex) {
            Logger.getLogger(ConnectionHandler.class.getName()).log(Level.SEVERE, null, ex);
        } finally {
            try {
                ois.close();
            } catch (IOException ex) {
            }
        }
        return objects;
    }

    public synchronized void Dopisz(String file, Card.Vote[] glos) {
        try {
            if (new File(file).length() != 0) {
                ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
                Card.Vote[] objects = (Card.Vote[]) ois.readObject();
                ois.close();

                Card.Vote[] KK = new Card.Vote[objects.length + 1];
                for (int i = 0; i < objects.length; i++) {
                    KK[i] = objects[i];
                }
                KK[KK.length - 1] = glos[0];
                ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(file));
                oos.writeObject(KK);
                oos.flush();
                oos.close();
            } else {
                ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(file));
                oos.writeObject(glos);
                oos.flush();
                oos.close();
            }
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }

    public synchronized BigInteger[] OdczytKluczy(String file) {
        ObjectInputStream ois = null;
        BigInteger[] klucze = null;
        try {
            ois = new ObjectInputStream(new FileInputStream(file));
            klucze = (BigInteger[]) ois.readObject();
        } catch (ClassNotFoundException ex) {
            Logger.getLogger(KeyGen.class.getName()).log(Level.SEVERE, null, ex);
        } catch (IOException ex) {
            Logger.getLogger(KeyGen.class.getName()).log(Level.SEVERE, null, ex);
        } finally {
            try {
                ois.close();
            } catch (IOException ex) {
                Logger.getLogger(KeyGen.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
        return klucze;
    }
//    // dodaje
//    public static KeyStore loadKeyStore (String ksFile, String pwd) {
//       KeyStore ks = null;
//       try {
//          ks = KeyStore.getInstance( KeyStore.getDefaultType() );
//          InputStream stream = new FileInputStream( ksFile );
//          ks.load( stream, pwd.toCharArray() );
//           // Get certificate
//          java.security.cert.Certificate cert = ks.getCertificate("server");
//          System.out.println(cert.getPublicKey());
//       } catch (Exception ex) {
//          ex.printStackTrace();
//       }finally{
//           return ks;
//       }
//}
    //koniec
}
